package rdbms;

import java.util.ArrayList;
import java.util.List;

import rdbms.ddl.Field;
import rdbms.ddl.Table;
import valuetypes.BooleanValue;
import valuetypes.CharacterValue;
import valuetypes.DBValue;
import valuetypes.GroupValue;
import valuetypes.NumericValue;
import exception.DBValueException;
import exception.InvalidFieldException;
import exception.NoSuchDatabaseException;

public class Group {

	
	private Field[] fields;
	private CharacterValue name;
	private Group refersTo;
	private ArrayList<Group> referers;
	private Table table;
	private NumericValue groupId;
	private BooleanValue isPrimaryKey;
	private BooleanValue isUnique;
	
	public Group(int id, String name, Table table, Field[] fields, boolean isPrimary, boolean unique) throws DBValueException, InvalidFieldException {
		this.fields = fields;
		this.name = new CharacterValue(name);
		this.table = table;
		for(Field f : fields){
			if(! table.containsField(f) ){
				throw InvalidFieldException.NoSuchField(f.getName(), table);
			}
		}
		refersTo = null;
		referers = new ArrayList<Group>();
		isPrimaryKey = new BooleanValue(isPrimary);
		isUnique = new BooleanValue(isPrimary || unique);
		groupId = new NumericValue(id);
	}
	
	private Group(NumericValue id, CharacterValue name, Table table,
			BooleanValue isPrimary, BooleanValue unique) {
		this.groupId = id;
		this.name = name;
		this.table = table;
		isPrimaryKey = isPrimary;
		isUnique = unique;
		fields = new Field[0];
	}

	public boolean matches(Group other){
		if(this.fields.length == other.fields.length){
			for(int i = 0; i < fields.length; i++){
				if(fields[i].getFieldType() != other.fields[i].getFieldType()){
					return false;
				}				
			}
			return true;
		}
		else return false;		
	}
	
	public NumericValue getID(){
		return groupId;
	}
	
	public boolean referTo(Group other){
		if(this.matches(other)){
			refersTo = other;
			other.referers.add(this);
			return true;
		}
		else return false;
	}
	
	public boolean validate(Entry validatingEntry){
		GroupValue validatingValue = validatingEntry.getGroupValue(this);
		if(isUnique.getBool()){			
			ArrayList<Entry> existing = new ArrayList<>(table.getEntries());
			for(Entry e : existing){
				if(e.getGroupValue(this).equals(validatingValue)){
					return false;
				}
			}				
		}
		if(refersTo != null){
			if(this.fields.length == 1 && fields[0].isNullable() && validatingValue.getSubValues()[0].equals(0))return true;
			Table referredTable = refersTo.getTable();
			ArrayList<Entry> referedEntries = new ArrayList<Entry>(referredTable.getEntries());
			for(Entry e : referedEntries){
				GroupValue refValue = e.getGroupValue(refersTo);
				if(refValue.equals(validatingValue)){
					return true;
				}
			}
			return false;			
		}		
		return true;
	}

	public Field[] getFields() {
		return fields;
	}

	public byte[] serialize() {
		NumericValue refId = new NumericValue(0);//No reference
		if(refersTo != null) refId = refersTo.groupId;
		return DBValue.valuesAsData(groupId, table.getDatabase().getId(), table.getId(), refId, name, isPrimaryKey, isUnique);		
	}
	
	@Override
	public String toString() {
		NumericValue refId = new NumericValue(0);//No reference
		if(refersTo != null) refId = refersTo.groupId;
		return 	groupId + "\t| " + 
				table.getDatabase().getId() + "\t| " + 
				table.getId() + "\t| " + 
				refId + "\t| " + 
				name + "\t| " + 
				isPrimaryKey;
	}

	public Table getTable() {
		return table;
	}
	
	private void addField(Field f){
		int  length = fields.length;
		Field[] newFields = new Field[length + 1];
		System.arraycopy(fields, 0, newFields, 0, length);
		newFields[length] = f;
		fields = newFields;		
	}
	
	public static Group readFromData(byte[] data, RDBMS rdbms) throws DBValueException, NoSuchDatabaseException{
		int offset = 0;
		NumericValue id = new NumericValue(data, offset);
		offset += id.getSize();
		NumericValue databaseId = new NumericValue(data, offset);
		offset += databaseId.getSize();
		NumericValue tableId = new NumericValue(data, offset);
		offset += tableId.getSize();
		NumericValue referenceId = new NumericValue(data, offset);
		offset += referenceId.getSize();
		CharacterValue name = new CharacterValue(data, offset);
		offset += name.getSize();
		BooleanValue isPrimary = new BooleanValue(data, offset);
		offset += isPrimary.getSize();
		BooleanValue isUnique = new BooleanValue(data, offset);
		
		Table table = rdbms.getDatabase(databaseId).getTable(tableId) ;
		Group g = new Group(id, name, table, isPrimary, isUnique);
		return g;
	}
	
	public static void readAllFromData(List<byte[]> groupData, List<byte[]> columnData, RDBMS rdbms, boolean verbose) throws DBValueException, NoSuchDatabaseException{
		
		for(byte[] data : groupData){
			Group g = readFromData(data, rdbms);
			g.getTable().addGroup(g);			
			if(verbose){				
				System.out.println("Group: " + g);
			}
		}
		
		for(byte[] data : columnData){
			int offset = 0;
			NumericValue gcId = new NumericValue(data, offset);
			offset += gcId.getSize();
			
			NumericValue dbId = new NumericValue(data, offset);
			offset += dbId.getSize();
			
			NumericValue tableId = new NumericValue(data, offset);
			offset += tableId.getSize();
			
			NumericValue gId = new NumericValue(data, offset);
			offset += gId.getSize();
			
			NumericValue fieldId = new NumericValue(data, offset);
			offset += fieldId.getSize();
			
			Table table = rdbms.getDatabase(dbId).getTable(tableId);
			Group g = table.getGroup(gId);
			g.addField(table.getField(fieldId));
			
			if(verbose){
				System.out.println("GroupColumn: " + gcId + "\t| " + dbId + "\t| " + tableId + "\t| " + gId + "\t| " + fieldId);
			}
		}
		
		
	}

	public boolean isPrimaryKey() {
		return isPrimaryKey.getBool();
	}

	public boolean isReferredByOtherTable() {
		for(Group g : referers){
			if( !g.table.equals(this.table) )return true;
		}
		return false;
	}

	public List<Group> getReferrals() {
		return referers;
	}

	public boolean refersTo(GroupValue gv) {
		for(Entry e : table.getEntries()){
			if(gv.equals(e.getGroupValue(this)))return true;
		}
		return false;
	}

	public void cleanUp() {
		if(refersTo != null){
			refersTo.referers.remove(this);
		}
		
	}



}
